#include "symtab.h"
#include "gtree.h"
#include <iostream>
#include <fstream>
#include <sstream>
#include "syntax.tab.hpp"
#include "check.h"

using namespace std;
Field global;
vector<Field> fields = {global};
vector<Function> functions;
extern ofstream irout,asmout;
extern stringstream ss;
extern stringstream textss,datass;
int esp_offset = 0;

string get_tag();

int init() {
    Function read, write;
    read.type = T_FUNCTION;
    write.type = T_FUNCTION;
    read.return_type = T_NUM_INT;
    write.return_type = T_NUM_INT;
    read.name = "read";
    write.name = "write";
    Array p;
    p.type = T_INT;
    write.params.push_back(p);
    functions.push_back(read);
    functions.push_back(write);
    return 1;
}

int initialized = init();
vector<Struct> structs;
vector<Array> params;
vector<Return> returns;

Specifier resolve_specifier(GTNode *specifier) {
    Specifier s;
    s.line = specifier->lineno;
    if (specifier->child->token == TYPE) {
        if (specifier->child->name == "int")
            s.type = T_INT;
        else s.type = T_FLOAT;
    } else {
        s.type = T_STRUCT;
        if (specifier->child->child->sibiling->child)
            s.name = specifier->child->child->sibiling->child->id;
        else
            s.name = specifier->child->child->name;
    }
    return s;
}

Array resolve_VarDec(GTNode *VarDec) {
    Array p;
    if (VarDec->child->child) {
        p.type = T_ARR;
        while (VarDec->child->child) {
            p.dimen++;
            p.sizes.push_back(VarDec->child->sibiling->sibiling->ivalue);
            VarDec = VarDec->child;
        }
    }
    p.name = VarDec->child->id;
    p.lineno = VarDec->lineno;
    return p;
}

int param_offset = 0;

void getParams(GTNode *var_list) {
    while (var_list && var_list->token != RP) {
        GTNode *ParamDec = var_list->child;
        GTNode *VarDec = ParamDec->child->sibiling;
        Specifier var_specifier;
        //Todo Making using of resolve_specifier to extract var specifier
        if (ParamDec->child->child->token == TYPE) {
            var_specifier.type = (ParamDec->child->child->name == "int") ? T_INT : T_FLOAT;
        } else {
            var_specifier.name = ParamDec->child->child->sibiling->child->id;
            var_specifier.type = T_STRUCT;
        }
        Array a = resolve_VarDec(VarDec);
        a.type = Type(a.type | var_specifier.type);
        a.struct_name = var_specifier.name;
        a.offset = param_offset;
        params.push_back(a);
        param_offset += 4;
        if (ParamDec->sibiling)
            var_list = ParamDec->sibiling->sibiling;
        else
            break;
    }
}

int check_id(ChkType type, string name, int line, Field f) {

    for (int i = 0; i < f.primitives.size(); i++) {
        if (name == f.primitives[i].name) {
            Error(3, line, "previous defined @ " + to_string(f.primitives[i].lineno));
            return -1;
        }
    }
    for (int i = 0; i < f.arrays.size(); i++) {
        if (name == f.arrays[i].name) {
            Error(3, line, "previous defined @ " + to_string(f.arrays[i].lineno));
            return -1;
        }
    }
    if (type == C_FUNC || type == C_STRU) {
        for (int i = 0; i < functions.size(); i++) {
            if (name == functions[i].name) {
                Error(4, line, "previous defined @ " + to_string(functions[i].lineno));
                return -1;
            }
        }
        for (int i = 0; i < structs.size(); ++i) {
            if (name == structs[i].name) {
                Error(16, line, "previous defined @ " + to_string(structs[i].lineno));
                return -1;
            }
        }
    }
    return 0;
}

int check_stru_refer(string name, int line) {
    for (int i = 0; i < structs.size(); i++) {
        if (structs[i].name == name)
            return 0;
    }
    Error(17, line, "struct undefined");
    return -1;
}

int check_stru_field(string name, Struct stru, int line) {
    for (int i = 0; i < stru.fields.size(); i++) {
        if (stru.fields[i].name == name) {
            Error(15, line, "field redefinition");
            return -1;
        }
    }
    return 0;
}

int get_stru_size(string name) {
    for (int i = 0; i < structs.size(); i++) {
        if (structs[i].name == name)
            return structs[i].size;
    }
    return 0;
}

int get_arr_size(Array array) {
    int result = 1;
    for (int i = 0; i < array.dimen; i++) {
        result *= array.sizes[i];
    }
    return result;
}


void local_var_def(GTNode *node) //Def
{
    Field field = fields.back();
    fields.pop_back();
    GTNode *specifier = node->child;
    Specifier s = resolve_specifier(specifier);
    if (s.type == T_STRUCT)
        if (check_stru_refer(s.name, s.line))
            s.type = T_ERR;
    GTNode *dec_list = specifier->sibiling;
    while (1) {
        GTNode *dec = dec_list->child;
        Array p = resolve_VarDec(dec->child);
        if (!check_id(C_VAR, p.name, p.lineno, field)) {
            s.line = p.lineno;
            if (p.type == T_ARR) {//是数组
                p.struct_name = s.name;
                p.type = Type(p.type | s.type);
                p.offset = esp_offset;
                field.arrays.push_back(p);
                int arrsize = get_arr_size(p);
                if (s.type == T_STRUCT) {
                    int size = get_stru_size(s.name);
                    int total_size = size * arrsize;
                    ss << "DEC " << p.name << " " << total_size << endl;
                    esp_offset += total_size;
                } else {
                    ss << "DEC " << p.name << " " << 4 * arrsize << endl;
                    esp_offset += 4 * arrsize;
                }
            } else {
                if (s.type == T_STRUCT) {
                    int stru_size = get_stru_size(s.name);
                    ss << "DEC " << p.name << " " << stru_size << endl;
                    field.primitives.push_back(Primitive(s, p.name, esp_offset));
                    esp_offset += stru_size;
                } else {
                    field.primitives.push_back(Primitive(s, p.name, esp_offset));
                    esp_offset += 4;
                }
            }
        }
        //TODO  数组初始化
        if (dec->sibiling) {
            dec_list = dec->sibiling->sibiling;
        } else {
            break;
        }
    }
    fields.push_back(field);
}

void global_var_def(GTNode *node) //ExtDef
{
    Field field = fields.back();
    fields.pop_back();
    GTNode *specifier = node->child;
    Specifier s = resolve_specifier(specifier);
    if (s.type == T_STRUCT)
        if (check_stru_refer(s.name, s.line))
            s.type = T_ERR;
    GTNode *ExtDecList = specifier->sibiling;
    while (1) {
        GTNode *VarDec = ExtDecList->child;
        Array p = resolve_VarDec(VarDec);
        if (!check_id(C_VAR, p.name, p.lineno, field)) {
            s.line = p.lineno;
            if (p.type == T_ARR) {
                p.type = Type(p.type | s.type);
                if (p.type == T_STRUCT_ARR)
                    p.struct_name = s.name;
                field.arrays.push_back(p);
            } else {
                if (s.type == T_STRUCT)
                    p.struct_name = s.name;
                field.primitives.push_back(Primitive(s, p.name, 0));
            }
        }
        if (VarDec->sibiling)
            ExtDecList = VarDec->sibiling->sibiling;
        else
            break;
    }
    fields.push_back(field);
}
void func_def(GTNode *node) //ExtDef
{
    Function func;
    Specifier return_specifier = resolve_specifier(node->child);
    GTNode *FunDec = node->child->sibiling;
    func.name = FunDec->child->id;
    func.lineno = FunDec->child->lineno;
    if (!check_id(C_FUNC, func.name, func.lineno, fields.back())) {
        irout << "FUNCTION " << func.name << " :" << endl;
        for (int i = 0; i < params.size(); i++) {
            irout << "PARAM " << params[i].name << endl;
        }
        func.params = params;
        params.clear();
        func.param_offset = param_offset;
        func.offset = esp_offset;
        FunDec->offset = param_offset;
        param_offset = 0;
        esp_offset = 0;
        func.return_type = return_specifier.type;
        func.return_struct_name = return_specifier.name;
    }
    for (int i = 0; i < returns.size(); i++) {
        if (returns[i].type == T_NUM_INT)
            if (return_specifier.type == T_INT)
                continue;
        if (returns[i].type == T_NUM_FLOAT)
            if (return_specifier.type == T_FLOAT)
                continue;
        if (returns[i].type != return_specifier.type)
            Error(8, returns[i].line, "Type mismatched for return.");
    }
    returns.clear();
    functions.push_back(func);
}

void struct_def(GTNode *node) //StructSpecifier
{
    Struct stru;
    stru.type = T_STRUCT;
    stru.lineno = node->lineno;
    string stru_name;
    GTNode *OptTag = node->child->sibiling;
    if (OptTag->child)
        stru_name = OptTag->child->id;
    else
        stru_name = "$" + to_string(stru.lineno);
    stru.name = stru_name;
    if (!check_id(C_STRU, stru_name, node->lineno, fields.back())) {
        node->child->name = stru_name;//将结构体名称保存到STRUCT节点
        GTNode *def_list = OptTag->sibiling->sibiling;
        while (def_list->child) {
            GTNode *def = def_list->child;
            Specifier s = resolve_specifier(def->child);
            GTNode *dec_list = def->child->sibiling;
            while (1) {
                GTNode *VarDec = dec_list->child;
                Array array = resolve_VarDec(VarDec);
                if (!check_stru_field(array.name, stru, array.lineno)) {
                    stru.fields.push_back(StructField(s, array));
                    if (s.type == T_INT || s.type == T_FLOAT) {
                        if (array.type == T_ARR) {
                            stru.size = 4 * get_arr_size(array);
                        } else
                            stru.size += 4;
                    } else {
                        int stru_size = get_stru_size(s.name);
                        if (array.type == T_ARR) {
                            stru.size = stru_size * get_arr_size(array);
                        } else
                            stru.size += stru_size;
                    }
                }
                if (VarDec->sibiling) {
                    dec_list = VarDec->sibiling->sibiling;
                } else {
                    break;
                }
            }
            def_list = def->sibiling;
        }
        structs.push_back(stru);
    }
}


void save_return_type(GTNode *node) {
    Return r;
    r.type = node->type;
    r.line = node->lineno;
    returns.push_back(r);
}